<html>
<head>
  <title>14.命令（Command）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="23705"/>
<h1>14.命令（Command）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/7/24 16:22</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/1/29 6:58</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-size: 14pt; font-weight: bold;">一、基本描述</span></div><ul><li><div>将一个请求封装为一个对象，从而使你可用不同的请求对客户进行参数化，对请求排队或记录请求日志，以及支持可撤销操作。</div></li><li><div>队列、日志和支持撤销操作时基本命令模式相当直接的扩展。</div></li><li><div>又称为动作（action）模式，事物（transaction）模式</div></li><li><div>有时必须向某对象提交请求，但并不知道关于被请求的操作或请求的接收者的任何信息，命令模式将请求本身变成一个对象，这个对象可被存储并像其他对象一样被传递。</div></li><li><div>命令模式的关键是一个抽象的Command类，它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作，具体的Command子类将接收者作为它的一个实例变量，并实现Execute操作，指定接收者采取的动作，而接收者有执行该请求所需的具体信息。</div></li><li><div>可以将几个命令组成更大的命令的形式来支持命令脚本。</div></li><li><div>命令模式中，提交一个请求的对象仅需要知道如何提交它，而不需要知道该请求将会被如何执行。</div></li><li><div>命令模式的适用性：</div></li></ul><div style="padding-left: 30px;">1）需要抽象出待执行的动作参数化某对象。可用回调（CallBack）函数表达这种参数化机制。</div><div style="padding-left: 30px;">2）在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。</div><div style="padding-left: 30px;">3）支持取消操作。Command的Execute操作可在实施操作前将状态存储起来，在取消操作时，这个状态用来消除该操作的影响。</div><div style="padding-left: 30px;">4）支持修改日志。当系统崩溃时，这些修改可以被重做一遍。</div><div style="padding-left: 30px;">5）<span style="font-weight: bold;">用构建在原语操作上的高层操作构造一个系统。</span>这样一种结构在支持事务的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口，使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。</div><ul><li><div><span style="font-weight: bold;">应避免撤销操作过程中的错误累积</span>。在实现一个可靠的、能保持原先语义的撤销/重做机制时，可能会遇到滞后影响的问题。由于命令的重复执行、取消执行和重执行的过程中可能会积累错误，以致一个应用的状态最终偏离初始值。这就有必要在Command中存入更多的信息，以保证这些对象可被精确的复原成他们的初始状态。</div></li><li><div>对请求排队或记录请求日志，以及支持可撤销的操作等行为时，“行为请求者”与“行为实现者”的紧耦合是不太适合的。</div></li><li><div>敏捷开发的原则告诉我们，不要为代码添加基于猜测的，实际不需要的功能，如果不清楚一个系统是否需要命令模式，一般就不要着急去实现它，事实上，在需要的时候通过重构实现这个模式并不困难，只有在真正需要如撤销/恢复操作等功能时，把原来的代码重构为命令模式才有意义。</div></li><li><div>命令模式是一个高内聚的模式。</div></li><li><div>命令模式在项目中非常频繁的使用，因为它的封装性非常好，把请求方（Invoker）和执行方（Receiver）分开，扩展性也有很好的保障，通用代码比较简单。</div></li><li><div>在命令模式中，发送者与接收者之间引入了新的命令对象，将发送者的请求封装在命令对象中，再通过命令对象来调用接收者的方法。</div></li><li><div><span style="font-weight: bold;">命令模式的核心在于引入了命令类，通过命令类来降低发送者和接收者的耦合度，</span>请求发送者只需要指定一个命令对象，再通过命令对象调用请求接收者的处理方法。</div></li><li><div><span style="font-weight: bold;">命令模式的本质是对请求进行封装</span>，一个请求对应于一个命令，将发出命令的责任和执行命令的责任分隔开。</div></li><li><div>命令模式将封装带到一个全新的境界：把方法调用封装起来。</div></li><li><div>命令模式中，利用命令对象，可以把请求封装成一个特定对象。</div></li><li><div>命令模式可以类比餐厅：Invoker 对象类比女招待，Command 对象类比订单，Receiver 对象类比厨师。餐厅的女招待通过订单与厨师解耦。</div></li><li><div>命令可以将运算块打包（一个接收者和一组动作），然后将它传来传去，就像是一般的对象一样。现在，即使在命令对象被创建许久之后，运算依然可以被调用。我们可以利用这样的特性衍生一些应用，例如：日程安排、线程池、工作队列等。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="14.命令（Command）模式_files/截图录屏_选择区域_20200726130936.png" type="image/png" data-filename="截图录屏_选择区域_20200726130936.png" width="832"/></div><ul><li><div>Command：声明执行操作的接口。（类比订单）调用命令对象的execute()方法，就可以让接收者进行相关的动作。这个接口也可以拥有一个undo()方法。</div></li><li><div>ConcreteCommand：将一个接收者对象绑定于一个动作，调用接收者相应的操作，以实现Execute（接收命令，转发给receiver）。一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点，命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法，当此方法被调用的时候，接收者就会进行这些动作。从外面看，其他对象不知道究竟哪个接收者进行了哪些动作，只知道如果调用execute()方法，请求的目的就能达到。</div><div>ConcreteCommand定义了动作和接收者之间的绑定关系。调用者只要调用execute()就可以发出请求，然后由ConcreteCommand调用接收者的<span style="font-weight: bold;">一个或多个动作</span>。</div></li><li><div>Client：创建一个具体命令对象并设定它的接收者。（类比顾客）</div></li><li><div>Invoker：要求该命令执行这个请求。调用者，请求发送者，它通过命令对象来执行请求，一个调用者不需要在设计时确定其接收者，因此它只需要与抽象命令类之间存在关联关系即可，在程序运行时可以将一个具体命令对象注入其中，再调用具体命令对象的execute接口方法，从而实现间接调用请求接收者的相关操作。（类比女招待）</div><div>持有一个命令对象，并在某个时间点调用命令对象的execute()方法，将请求付诸执行。</div></li><li><div>Receiver：知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。Receiver可能是一个抽象类，因为接收者可能有多个——实际上，任何类无论是否有关系，都可以是Receiver。（类比厨师）</div></li><li><div>协作（书上标准协作）：</div></li></ul><div style="padding-left: 30px;">1）Client创建一个ConcreteCommand对象并指定它的receiver对象。</div><div style="padding-left: 30px;">2）某Invoker对象存储该ConcreteCommand对象。</div><div style="padding-left: 30px;">3）某Invoker通过调用Command对象的Execute操作来提交一个请求，若该命令是可撤销的，ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令。</div><div style="padding-left: 30px;">4）ConcreteCommand对象调用它的Receiver的一些操作以执行该请求。</div><div><span style="font-size: 14pt; font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>Command模式将调用操作的对象与知道如何实现该操作的对象解耦。将请求一个操作的对象与知道怎么执行一个操作的对象分割开。</div><div>将“动作的请求者”从“动作的执行者”对象中解耦。将“发出请求的对象”和“接受与执行这些请求的对象”分隔开来。</div></li><li><div>Command是头等的对象。它们可像其他的对象一样被操纵和扩展。</div></li><li><div>命令模式可以较容易的设计一个命令队列。可将多个命令装配成一个组合命令。一般说来，组合命令是Composite模式的一个实例。</div></li><li><div>增加新的Command很容易，因为这无需改变已有的类。</div></li><li><div>在需要的情况下，可以较容易的将命令记入日志。</div></li><li><div>允许接收请求的一方决定是否要否决请求。</div></li><li><div>可以容易的实现对请求的撤销与重做。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>类膨胀问题：对于Command的子类，如果有N个命令，Command的子类就要有N个，类膨胀问题比较严重。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">四、可能的改进</span></div><ul><li><div>命令队列</div></li><li><div>撤销操作</div><div>Command类的改进：当命令支持撤销时，该命令就必须提供和execute()方法相反的undo()方法。不管execute()刚才做什么，undo()都会倒转过来。</div><div>Invoker类的改进：加入一个新的实例变量，用来追踪最后被调用的命令，然后，不管何时撤销按钮被按下，我们都可以取出这个命令并调用它的undo()方法。</div></li><li><div>请求日志</div></li><li><div>Meta Command Pattern：可以创建命令的宏，以便一次执行多个命令。</div><div>宏命令：宏命令又称为组合命令，是组合模式与命令模式联用的产物。当调用宏命令的execute方法时，将递归调用它所包含的每个成员命令的execute方法，一个宏命令将触发多个具体命令的执行，从而实现对命令的批处理。</div><div>可以制造一种新的命令，用来执行其他一对命令，而不只是执行一个命令。</div></li></ul><div><span style="font-size: 14pt; font-weight: bold;">五、个人理解</span></div><ul><li><div>命令模式很常用，但是实现起来比较复杂。</div></li><li><div>命令模式的实现有很多种，特别体现在Invoker与Commad关联，Command与Receiver关联的实现位置。这充分体现了对于命令模式，思路比如何实现更重要。</div></li><li><div>命令模式应该可以扩展到大型系统的架构中。</div></li><li><div>命令模式的实现中，有一个Invoker类，一般情况下（简单场景中），似乎这个类是多余的、累赘的，但是对于稍微复杂的情况，特别是有改进的情况，通过Invoker来调用Command就会非常合理。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div></body></html> 